/*
XPTracker - Alternative Agile Tracking Tool
Copyright (C) 2006-2009  Stephen Starkey

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
*/
package org.xptracker.web;

import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.Provider;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.exception.ExceptionUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.xptracker.domain.CurrentUserProvider;
import org.xptracker.domain.Domain;
import org.xptracker.domain.User;
import org.xptracker.services.auth.Authenticator;
import org.xptracker.util.UserConfigInt;
import static org.xptracker.util.UserConfigString.*;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.List;

public class LoginFilter implements Filter {
  private static final String LOGGED_IN = "LoginFilter.loggedIn";
  private static final String LOGIN_JSP = "LoginFilter.LoginJsp";
  private static final String EXCLUSIONS = "LoginFilter.Exclusions";

  @Inject
  private static Injector injector;

  @SuppressWarnings({"UnusedDeclaration"})
  @Inject
  private static Provider<Domain> domainProvider;

  @SuppressWarnings({"UnusedDeclaration"})
  @Inject
  private static Authenticator authenticator;

  @SuppressWarnings({"UnusedDeclaration"})
  @Inject
  private static CurrentUserProvider currentUserProvider;

  private final List<String> exclusions = new ArrayList<String>();
  private String loginJsp;

  public void init(FilterConfig filterConfig) throws ServletException {
    loginJsp = filterConfig.getInitParameter(LOGIN_JSP);
    this.exclusions.add(loginJsp);
    String exclusions = filterConfig.getInitParameter(EXCLUSIONS);
    if (StringUtils.isNotBlank(exclusions)) {
      for (String exclusion : exclusions.split(" ")) {
        String trimmed = exclusion.trim();
        if (trimmed.length() > 0) {
          this.exclusions.add(trimmed);
        }
      }
    }
  }

  public void destroy() {
  }

  public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException {
    if (req instanceof HttpServletRequest) {
      HttpServletRequest httpReq = ((HttpServletRequest) req);
      HttpServletResponse httpResp = (HttpServletResponse) resp;
      String uri = httpReq.getRequestURI();
      String localUri = uri.substring(httpReq.getContextPath().length());
      boolean allowed = false;
      for (String exclusion : exclusions) {
        if (localUri.startsWith(exclusion)) {
          allowed = true;
        }
      }

      String userId = getLoggedInUser(httpReq);
      if (StringUtils.isNotBlank(userId)) {
        LogFactory.getLog(getClass()).debug("Request from user '" + userId + "'");
        httpReq.setAttribute("loggedIn", true);
        allowed = true;
      }

      if (allowed) {
        HttpSession session = httpReq.getSession();
        if (session.isNew()) {
          session.setMaxInactiveInterval(new UserConfigInt(TIMEOUT, PROPS_FILE, DEFAULT_TIMEOUT).get());
        }
        
        RefreshingUser user = new RefreshingUser(userId);
        injector.injectMembers(user);
        httpReq.setAttribute("user", RefreshingUser.makeAccessor(user));
        currentUserProvider.setCurrentUser(userId);
        chain.doFilter(req, resp);
      } else {
        httpResp.sendRedirect(httpReq.getContextPath() + loginJsp + "?url=" + getUrl(httpReq));
      }
    } else {
      resp.setContentType("text/plain");
      resp.getOutputStream().print("XPTracker will not run outside an HTTP context.");
    }
  }

  private String getUrl(HttpServletRequest httpReq) throws UnsupportedEncodingException {
    String requestURI = httpReq.getRequestURI();
    String queryString = httpReq.getQueryString();
    if (StringUtils.isNotBlank(queryString)) {
      requestURI += ("?" + queryString);
    }
    return URLEncoder.encode(requestURI, "UTF-8");
  }

  public static LoginFailure attemptLogin(Authenticator authenticator, Domain domain, HttpServletRequest request,
                                          String username, String password) {
    Log log = LogFactory.getLog(LoginFilter.class);
    log.debug(MessageFormat.format("Attempting login: ''{0}''", username));

    User user = domain.loadUser(username);
    if (user != null && user.isEnabled()) {
      try {
        if (authenticator.isAuthenticated(user, password)) {
          request.getSession().setAttribute(LOGGED_IN, user.getId());
          return null;
        } else {
          return new LoginFailure(LoginField.password, "Invalid username/password combination.");
        }
      } catch (Exception e) {
        log.error("Error authenticating", e);
        return new LoginFailure(LoginField.username, "Authentication mechanism configured incorrectly.");
      }
    } else {
      return new LoginFailure(LoginField.username, "User not found: ");
    }
  }

  public static void logout(HttpServletRequest request) {
    request.getSession().removeAttribute(LOGGED_IN);
  }

  private static String getLoggedInUser(HttpServletRequest request) {
    try {
      return (String) request.getSession().getAttribute(LOGGED_IN);
    } catch (NullPointerException e) {
      Log log = LogFactory.getLog(LoginFilter.class);
      log.error(e);
      log.error("############### Stack Trace #####################");
      log.error(ExceptionUtils.getFullStackTrace(e));
      throw e;
    }
  }
}
